﻿using IOP.Pulsar.MQTT.Abstractions;
using IOP.Pulsar.MQTT.Attributes;
using IOP.Pulsar.MQTT.Result;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Reflection;

namespace IOP.Pulsar.MQTT
{

    /// <summary>
    /// 主题路由
    /// </summary>
    public class ControllerRouter : IControllerRouter
    {
        /// <summary>
        /// 控制器集合
        /// </summary>
        public ConcurrentDictionary<Type, ConcurrentQueue<MQTTController>> Controllers { get; set; } = new ConcurrentDictionary<Type, ConcurrentQueue<MQTTController>>();

        /// <summary>
        /// 路由字典
        /// </summary>
        private ConcurrentDictionary<string, MethodInfo> _Routers { get; set; } = new ConcurrentDictionary<string, MethodInfo>();

        /// <summary>
        /// 构造函数
        /// </summary>
        public ControllerRouter()
        {
            CreateRouter();
        }
        /// <summary>
        /// 获取控制器对应方法
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public MethodInfo GetControllerMethod(string filter)
        {
            _Routers.TryGetValue(filter, out MethodInfo method);
            return method;
        }

        /// <summary>
        /// 创建路由
        /// </summary>
        private void CreateRouter()
        {
            var entry = Assembly.GetEntryAssembly();
            var classes = entry.GetTypes();
            var defaultloop = new ConcurrentQueue<MQTTController>();
            Controllers.TryAdd(typeof(MQTTController), defaultloop);
            _Routers.TryAdd(MQTTConstant.ClusterPublish, typeof(MQTTController).GetMethod("ClusterPublish"));
            foreach(var item in classes)
            {
                if(item.BaseType == typeof(MQTTController))
                {
                    var loop = new ConcurrentQueue<MQTTController>();
                    Controllers.TryAdd(item, loop);
                    var methods = item.GetMethods(BindingFlags.Public | BindingFlags.Instance);
                    foreach(var method in methods)
                    {
                        if(method.CustomAttributes.Any(x=>x.AttributeType == typeof(PUBLISHAttribute)))
                        {
                            if (method.ReturnType != typeof(PublishResult)) throw new Exception("Publish method must be return PublishResult");
                            var publish = method.GetCustomAttribute<PUBLISHAttribute>();
                            _Routers.AddOrUpdate(publish.TopicFilter, method, (key, oldMethod) => method);
                        }
                        else if(method.CustomAttributes.Any(x => x.AttributeType == typeof(CONNECTAttribute)))
                        {
                            if (method.ReturnType != typeof(ConnectResult)) throw new Exception("Connect method must be return ConnectResult");
                            if (_Routers.ContainsKey(MQTTConstant.Connect)) continue;
                            _Routers.TryAdd(MQTTConstant.Connect, method);
                        }
                        else if (method.CustomAttributes.Any(x => x.AttributeType == typeof(PUBACKAttribute)))
                        {
                            if (method.ReturnType != typeof(PubackResult)) throw new Exception("Puback method must be return PubackResult");
                            if (_Routers.ContainsKey(MQTTConstant.Puback)) continue;
                            _Routers.TryAdd(MQTTConstant.Puback, method);
                        }
                        else if (method.CustomAttributes.Any(x => x.AttributeType == typeof(PUBRECAttribute)))
                        {
                            if (method.ReturnType != typeof(PubrecResult)) throw new Exception("Pubrec method must be return PubrecResult");
                            if (_Routers.ContainsKey(MQTTConstant.Pubrec)) continue;
                            _Routers.TryAdd(MQTTConstant.Pubrec, method);
                        }
                        else if(method.CustomAttributes.Any(x => x.AttributeType == typeof(PUBRELAttribute)))
                        {
                            if (method.ReturnType != typeof(PubrelResult)) throw new Exception("Pubrel method must be return PubrelResult");
                            if (_Routers.ContainsKey(MQTTConstant.Pubrel)) continue;
                            _Routers.TryAdd(MQTTConstant.Pubrel, method);
                        }
                        else if (method.CustomAttributes.Any(x => x.AttributeType == typeof(PUBCOMPAttribute)))
                        {
                            if (method.ReturnType != typeof(PubcompResult)) throw new Exception("Pubcomp method must be return PubcompResult");
                            if (_Routers.ContainsKey(MQTTConstant.Pubcomp)) continue;
                            _Routers.TryAdd(MQTTConstant.Pubcomp, method);
                        }
                        else if(method.CustomAttributes.Any(x => x.AttributeType == typeof(SUBSCRIBEAttribute)))
                        {
                            if (method.ReturnType != typeof(SubscribeResult)) throw new Exception("Subscribe method must be return SubscribeResult");
                            if (_Routers.ContainsKey(MQTTConstant.Subscribe)) continue;
                            _Routers.TryAdd(MQTTConstant.Subscribe, method);
                        }
                        else if(method.CustomAttributes.Any(x => x.AttributeType == typeof(UNSUBSCRIBEAttribute)))
                        {
                            if (method.ReturnType != typeof(UnsubscribeResult)) throw new Exception("Unsubscribe method must be return UnsubscribeResult");
                            if (_Routers.ContainsKey(MQTTConstant.Unsubscribe)) continue;
                            _Routers.TryAdd(MQTTConstant.Unsubscribe, method);
                        }
                        else if(method.CustomAttributes.Any(x => x.AttributeType == typeof(PINGREQAttribute)))
                        {
                            if (method.ReturnType != typeof(PingreqResult)) throw new Exception("Pingreq method must be return PingreqResult");
                            if (_Routers.ContainsKey(MQTTConstant.Pingreq)) continue;
                            _Routers.TryAdd(MQTTConstant.Pingreq, method);
                        }
                        else if(method.CustomAttributes.Any(x => x.AttributeType == typeof(DISCONNECTAttribute)))
                        {
                            if (method.ReturnType != typeof(DisconnectResult)) throw new Exception("Disconnect method must be return DisconnectResult");
                            if (_Routers.ContainsKey(MQTTConstant.Disconnet)) continue;
                            _Routers.TryAdd(MQTTConstant.Disconnet, method);
                        }
                    }
                }
            }
        }
    }
}
